#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <pthread.h>
#include <signal.h>
#include "nameServer.h"
#include "Xchange.h"

void error(const char *msg) {
    perror(msg);
    exit(0);
}
/**********************************************************************************
 *  #define
 **********************************************************************************/

#define PORT 9004
#define BUFFSIZE 256
#define MAX_CLIENTS 50
#define MAX_DOWNLOADS 50 

struct thread_data {
    int thread_id;
    int cs;
    int block_size;
    int last_downloaded_block;
    char *file_name;
    int max_blocks;
};

typedef struct thread_data Thread_data;

int ms;

Thread_data thread_data_array[MAX_DOWNLOADS];
struct sockaddr_in gv_address; // struct sockeaddr_in defined in socket.h
pthread_t tarray[MAX_CLIENTS]; /* array of thread variables */
/**********************************************************************************
 *  function prototypes
 **********************************************************************************/

int init_socket(void);
void listen_and_handle(int s);
void close_socket();
void handle(void *);
void startDownloadFile(char *, char *, char *, char *, char *, Thread_data *);
char i_as_char;

/* -----------------------------------------------------------------------------------------------------------
 * Function:	init_socket
 * Purpose:	creates a socket and binds it to port PORT
 * Arguments: 	none
 * Return value:    Success: returns socket descriptor of new socket
 *		    Failure: none
 *------------------------------------------------------------------------------------------------------------
 */

int init_socket(void) {
    int rv; // return value
    int s = socket(AF_INET, SOCK_STREAM, 0);
    if (s < 0) {
        perror("Error creating socket");
        exit(EXIT_FAILURE);
    }

    gv_address.sin_family = AF_INET;
    gv_address.sin_addr.s_addr = INADDR_ANY;
    gv_address.sin_port = htons(PORT);

    rv = bind(s, (struct sockaddr *) &gv_address, sizeof (gv_address));
    if (rv < 0) {
        perror("Error binding to socket");
        exit(EXIT_FAILURE);
    }

    return s;
}

/* -----------------------------------------------------------------------------------------------------------
 * Function:	close_socket
 * Purpose:	closes a socket
 * Arguments: 	socket desciptor
 * Return value:    Success: none
 *		    Failure: none
 *------------------------------------------------------------------------------------------------------------
 */

void close_socket() {
    int rv = close(ms);
    if (rv < 0) {
        perror("Error closing socket");
        exit(EXIT_FAILURE);
    }
}

/* -----------------------------------------------------------------------------------------------------------
 * Function:	listen and handle on a socket
 * Purpose:	listen for incoming connetions; spawn thread for each incoming connection
 * Arguments: 	master socket desciptor
 * Return value: Success: none
 *		 Failure: none
 *------------------------------------------------------------------------------------------------------------
 */


void listen_and_handle(int ms) {
    int rv; // return value
    int cs; // child socket descriptor
    int i = 0;

    // listen for incoming connection, MAX_CLIENTS connections pending
    rv = listen(ms, MAX_CLIENTS);
    if (rv < 0) {
        perror("Error listening to socket");
        exit(EXIT_FAILURE);
    }
    while (1) {
        for (i = 0; i < MAX_CLIENTS; i++) {
            // accept incoming connection
            unsigned int addr_len = sizeof (gv_address);
            cs = accept(ms, (struct sockaddr *) &gv_address, &addr_len);
            if (cs < 0) {
                printf("Error accept");
                exit(EXIT_FAILURE);
            }
            thread_data_array[i].cs = cs;
            thread_data_array[i].thread_id = i;
            thread_data_array[i].file_name = "STOP";
            rv = pthread_create(&tarray[i], NULL, (void *) &handle, (void *) &thread_data_array[i]);
            if (rv != 0) {
                perror("Error creating thread");
                exit(EXIT_FAILURE);
            }
        }

        // now wait for all threads to terminate
        for (i = 0; i < MAX_CLIENTS; i++) {
            rv = pthread_join(tarray[i], NULL);
            if (rv != 0) {
                perror("Error listening to socket");
                exit(EXIT_FAILURE);
            }
        }
        i = 0;
    }
}

/* -----------------------------------------------------------------------------------------------------------
 * Function:	handle a socket (receive and send)
 * Purpose:	if a string "STOP" is received then close connection,
 *		else echoe received string back
 * Arguments: 	socket desciptor, thread id
 * Return value: Success: none
 *		 Failure: none
 *------------------------------------------------------------------------------------------------------------
 */

void handle(void* arg) {
    Thread_data *data = (Thread_data *) arg;
    int rc;
    int rv = 0; // return value
    int s = data->cs;

    // create and init a char buffer
    char *buffer = malloc(BUFFSIZE);
    buffer[0] = '\0';

    int stop_received = 0;
    int nr_bytes_recv = 0;

    while (!stop_received) {
        // receive data in buffer
        nr_bytes_recv = recv(s, buffer, BUFFSIZE, 0);

        if (nr_bytes_recv > BUFFSIZE) {
            perror("Buffer too small");
            exit(EXIT_FAILURE);
        }

        buffer[nr_bytes_recv] = '\0'; // close string
        printf("Server received : %s", buffer);
        const char dlm[] = " \n\r";
        char *token = strtok(buffer, dlm);
        if (strncmp(token, "STOP_ROUTER", 11) == 0) {

            int i = 1;
            while (data->thread_id > i) {
                if (strcmp(thread_data_array[i].file_name, "STOP") == 0) {

                } else {
                    pthread_cancel(tarray[i]);
                    int count = 0;
                    char info_file_array[thread_data_array[i].max_blocks + 1];
                    char *info_file = info_file_array;
                    strcat(info_file, ":");
                    while (thread_data_array[i].max_blocks >= count) {
                        if (thread_data_array[i].last_downloaded_block >= count) {
                            strcat(info_file, "1");
                        } else {
                            strcat(info_file, "0");
                        }
                        count++;
                    }
                    strcat(info_file, "\0");

                    char message[strlen(thread_data_array[i].file_name) + strlen(info_file) + 1];
                    char *messagePointer = message;
                    strcat(messagePointer, thread_data_array[i].file_name);
                    strcat(messagePointer, ":");
                    strcat(messagePointer, info_file);
                    strcat(messagePointer, "\n");
                    printf("%s", messagePointer);
                    rv = send(s, messagePointer, strlen(messagePointer), MSG_NOSIGNAL);
                }
                i++;
            }

            char *message = "DONE\n";
            printf("%s", message);
            rv = send(s, message, strlen(message), 0);
            if (rv < 0) {
                close_socket();
                perror("Error sending");
                exit(EXIT_FAILURE);
            }
            rv = close(s);
            stop_received = 1;
        } else if (strncmp(token, "REMOVERT", 8) == 0) {
            int i = 0;
            while (i < MAX_CLIENTS) {
                thread_data_array[i].block_size = 0;
                thread_data_array[i].cs = 0;
                thread_data_array[i].file_name = "";
                thread_data_array[i].last_downloaded_block = 0;
                thread_data_array[i].max_blocks = 0;
                thread_data_array[i].thread_id = 0;
                i++;
            }
        } else if (strncmp(token, "STOP", 4) == 0) {
            char *message = "Closing server socket...\n";
            printf("%s", message);
            rv = send(s, message, strlen(message), 0);
            if (rv < 0) {
                perror("Error sending");
                exit(EXIT_FAILURE);
            }

            rv = close(s);
            if (rv < 0) {
                close_socket();
                perror("Error closing socket");
                exit(EXIT_FAILURE);
            }

            stop_received = 1;
        } else if (strncasecmp(token, "DOWNLOAD", 8) == 0) {

            token = strtok(NULL, ",");
            char *name = token;
            token = strtok(NULL, ",");
            char *size = token;
            token = strtok(NULL, ",");
            char *nr_of_blocks = token;
            token = strtok(NULL, ",");
            char *ip = token;
            token = strtok(NULL, ";");
            char *info_file = token;

            char *message = "OK\n";
            rv = send(s, message, strlen(message), 0);
            if (rv < 0) {
                close_socket();
                error("ERROR writing to socket");

                exit(EXIT_FAILURE);
            }

            data->file_name = name;
            rv = close(s);
            stop_received = 1;
            startDownloadFile(name, size, nr_of_blocks, ip, info_file, data);

        } else {
            // echo message
            rv = send(s, buffer, nr_bytes_recv, 0);
            if (rv < 0) {
                close_socket();
                perror("Error sending");
                exit(EXIT_FAILURE);
            }
        }
    }
}

void startDownloadFile(char *name, char *size, char *nr_of_blocks, char *ip, char *info_file, Thread_data *data) {
    data->last_downloaded_block = 0;
    int blocks = atoi(nr_of_blocks) - 1;
    char *temp_info_file = malloc(strlen(info_file));
    strcpy(temp_info_file, info_file);
    int i = 0;
    while (blocks >= i) {
        printf("%s\n", temp_info_file);
        if (strcmp(temp_info_file, "0") >= 0 && strcmp(temp_info_file, "1") <= 0) {
            break;
        }
        data->last_downloaded_block++;
        temp_info_file++;
        i++;
    }
    data->max_blocks = blocks;
    printf("Startblok: %i Max: %i\n", data->last_downloaded_block, blocks);
    i = data->last_downloaded_block;
    while (blocks >= data->last_downloaded_block) {
        char* i_char = malloc(200);
        sprintf(i_char, "%d", data->last_downloaded_block);
        printf("%s\n", i_char);
        getRemoteBlock(9003, ip, name, i_char);
        data->last_downloaded_block++;
    }
}

void exit_all() {
    exit(EXIT_SUCCESS);
}

int main(int argc, char *argv[]) {
    (void) signal(SIGINT, exit_all);
    if (argc < 3) {
        fprintf(stderr, "usage %s hostname port\n", argv[0]);
        exit(0);
    }
    int portno = atoi(argv[2]);
    char *host = argv[1];
    /*
        char *buffer = listPeers(portno, host);
        char* token;
        const char dlm[] = " \n\r";
        token = strtok(buffer, dlm);
        while (token != NULL) {
            printf("%s\n", token);
            //searchFiles(9003,token,"fus.mp3");

            token = strtok(NULL, " \n\r");
        }
     */

    /*
        getRemoteBlock(9003, "192.168.1.180", "three-block.mp3", "0");
        getRemoteBlock(9003, "192.168.1.180", "three-block.mp3", "1");
        getRemoteBlock(9003, "192.168.1.180", "three-block.mp3", "2");
     */


    // master socket descriptor

    ms = init_socket();
    listen_and_handle(ms);
    close_socket();

    return 0;
}